﻿using System;
using Microsoft.Xna.Framework;

namespace GDD2_Game3
{
	/// <summary>
	/// A structure that adds on to the built-in XNA Rectangle. Allows for float values, and stores center of the rectangle as its position. Implicitly converts to and from XNA's Rectangle (where Rectangle's X and Y store the top-left corner, as opposed to FloatBox storing the center).
	/// </summary>
	struct FloatBox : IEquatable<FloatBox>
	{
		/// <summary>
		/// The position of the center of the FloatBox.
		/// </summary>
		public Vector2 Position;// { get; set; }

		/// <summary>
		/// The dimensions of the FloatBox (X is the Width and Y is the Height).
		/// </summary>
		public Vector2 Dimensions { get; private set; }

		/// <summary>
		/// The width of the FloatBox (stored in Dimensions).
		/// </summary>
		public float Width
		{
			get { return Dimensions.X; }
			private set { Dimensions = new Vector2(value, Dimensions.Y); }
		}

		/// <summary>
		/// The height of the FloatBox (stored in Dimensions).
		/// </summary>
		public float Height
		{
			get { return Dimensions.Y; }
			private set { Dimensions = new Vector2(Dimensions.X, value); }
		}

		/// <summary>
		/// Constructs a FloatBox using a position Vector2, a width, and a height.
		/// </summary>
		/// <param name="position">Position of the center of the FloatBox.</param>
		/// <param name="width">Width of the FloatBox.</param>
		/// <param name="height">Height of the FloatBox.</param>
		public FloatBox(Vector2 position, Vector2 dimensions) :
			this()
		{
			Position = position;
			Dimensions = dimensions;
		}

		/// <summary>
		/// Constructs a FloatBox using an x, a y, a width, and a height.
		/// </summary>
		/// <param name="x">X-coordinate of the center of the FloatBox.</param>
		/// <param name="y">Y-coordinate of the center of the FloatBox.</param>
		/// <param name="width">Width of the FloatBox.</param>
		/// <param name="height">Height of the FloatBox.</param>
		public FloatBox(float x, float y, float width, float height) :
			this(new Vector2(x, y), new Vector2(width, height))
		{
		}

		/// <summary>
		/// Gives a string representation for this FloatBox object.
		/// </summary>
		/// <returns>A string which gives details about this FloatBox object.</returns>
		public override string ToString()
		{
			return string.Format("FloatBox: Position {0}, Width {1}, Height {2}", Position, Width, Height);
		}
		
		/// <summary>
		/// Turns a FloatBox into a Rectangle to be used for drawing. The X and Y of the Rectangle returned denote the TOP-LEFT of the Rectangle.
		/// </summary>
		/// <param name="floatBox">The FloatBox to convert.</param>
		/// <returns>The converted Rectangle.</returns>
		public static implicit operator Rectangle(FloatBox floatBox)
		{
			return new Rectangle(Convert.ToInt32(floatBox.Position.X - floatBox.Width / 2),
				                                         Convert.ToInt32(floatBox.Position.Y - floatBox.Height / 2),
														 Convert.ToInt32(floatBox.Width),
														 Convert.ToInt32(floatBox.Height));
		}

		/// <summary>
		/// Turns a Rectangle into a FloatBox to be used in game logic calculations. The Position of the FloatBox returned denote the CENTER of the FloatBox.
		/// </summary>
		/// <param name="rectangle">The Rectangle to convert.</param>
		/// <returns>The converted FloatBox</returns>
		public static implicit operator FloatBox(Rectangle rectangle)
		{
			return new FloatBox(rectangle.X + rectangle.Width / 2, rectangle.Y + rectangle.Height / 2, rectangle.Width, rectangle.Width);
		}

		/// <summary>
		/// Adds a delta Vector2 to the Position of the FloatBox.
		/// </summary>
		/// <param name="floatBox">The FloatBox to add to.</param>
		/// <param name="deltaPosition">The delta position to add to the FloatBox.</param>
		/// <returns>The resulting FloatBox after the shift in position.</returns>
		public static FloatBox operator +(FloatBox floatBox, Vector2 deltaPosition)
		{
			return new FloatBox(floatBox.Position + deltaPosition, floatBox.Dimensions);
		}

		/// <summary>
		/// Subtracts a delta Vector2 from the Position of the FloatBox.
		/// </summary>
		/// <param name="floatBox">The FloatBox to subtract from.</param>
		/// <param name="deltaPosition">The delta position to subtract from the FloatBox.</param>
		/// <returns>The resulting FloatBox after the shift in position.</returns>
		public static FloatBox operator -(FloatBox floatBox, Vector2 deltaPosition)
		{
			return new FloatBox(floatBox.Position - deltaPosition, floatBox.Dimensions);
		}

		#region Equality Methods and Functions

		/// <summary>
		/// Compares this FloatBox to this method's argument to see if they have the same position and size.
		/// </summary>
		/// <param name="other">The object to compare the calling object to.</param>
		/// <returns>true if the FloatBoxs are equal; false otherwise. Also returns false if the argument is not a FloatBox.</returns>
		public override bool Equals(object other)
		{
			return other is FloatBox && Equals((FloatBox)other);
		}

		/// <summary>
		/// Compares this FloatBox to this method's argument to see if they have the same position and size.
		/// </summary>
		/// <param name="other">The FloatBox to compare the calling object to.</param>
		/// <returns>true if the FloatBoxs are equal; false otherwise.</returns>
		public bool Equals(FloatBox other)
		{
			return (Position == other.Position) && (Dimensions == other.Dimensions); 
		}

		/// <summary>
		/// Compares two FloatBoxs to see if they have the same position and size.
		/// </summary>
		/// <param name="floatBox1">The first FloatBox to test for equality.</param>
		/// <param name="floatBox2">The second FloatBox to test for equality.</param>
		/// <returns>true if the FloatBoxs are equal; false otherwise.</returns>
		public static bool operator ==(FloatBox floatBox1, FloatBox floatBox2)
		{
			return floatBox1.Equals(floatBox2);
		}

		/// <summary>
		/// Compares two FloatBoxs to see if they don't have the position and size.
		/// </summary>
		/// <param name="floatBox1">The first FloatBox to test for inequality.</param>
		/// <param name="floatBox2">The second FloatBox to test for inequality.</param>
		/// <returns>true if the FloatBoxs are not equal; false otherwise.</returns>
		public static bool operator !=(FloatBox floatBox1, FloatBox floatBox2)
		{
			return !floatBox1.Equals(floatBox2);
		}

		/// <summary>
		/// Defines a hash code for the FloatBox.
		/// </summary>
		/// <returns>The hash code of the FloatBox.</returns>
		public override int GetHashCode()
		{
			return Position.GetHashCode() ^ Dimensions.GetHashCode();
		}

		#endregion

		/*/// <summary>
		/// Creates a FloatBox given the top-left corner, rather than the center.
		/// </summary>
		/// <param name="topLeft">The top-left corner of the FloatBox.</param>
		/// <param name="dimensions">The dimensions of the FloatBox.</param>
		/// <returns>A FloatBox with the given top-left corner and dimensions.</returns>
		public static FloatBox createFromTopRight(Vector2 topLeft, Vector2 dimensions)
		{
			return new FloatBox(topLeft + dimensions / 2, dimensions);
		}

		/// <summary>
		/// Creates a FloatBox given the top-left corner, rather than the center.
		/// </summary>
		/// <param name="tlx">The x-coordinate of the top-left corner of the FloatBox.</param>
		/// <param name="tly">The y-coordinate of the top-left corner of the FloatBox.</param>
		/// <param name="width">The width of the FloatBox.</param>
		/// <param name="height">The height of the FloatBox.</param>
		/// <returns>A FloatBox with the given top-left corner and dimensions.</returns>
		public static FloatBox createFromTopRight(float tlx, float tly, float width, float height)
		{
			return createFromTopRight(new Vector2(tlx, tly), new Vector2(width, height));
		}*/

		/// <summary>
		/// Checks if two FloatBoxs intersect (the FloatBoxs must be overlapping to register as intersecting; FloatBoxs that simply touch without overlapping will not count as intersecting). Returns detailed information about the intersection (note: this method is NOT commutative; that is, a.intersects(b) is not the same as b.intersects(a)).
		/// </summary>
		/// <param name="other">The FloatBox to check intersection with.</param>
		/// <returns>IntersectionInformation object which details the directions the parameter intersects the caller (for example: if a hits b from above (that is, a is above b at the time of intersection), b.intersects(a).OtherAbove is true, while a.intersects(b) is not true).</returns>
		public IntersectionInformation intersects(FloatBox other)
		{
			Vector2 diffCentersDoubled = 2 * (other.Position - Position);
			float sumHeights = Height + other.Height;
			float sumWidths = Width + other.Width;
            bool otherAbove = diffCentersDoubled.Y > -sumHeights && diffCentersDoubled.Y < 0;
            bool otherBelow = diffCentersDoubled.Y < sumHeights && diffCentersDoubled.Y > 0;
            bool otherToLeft = diffCentersDoubled.X > -sumWidths && diffCentersDoubled.X < 0;
            bool otherToRight = diffCentersDoubled.X < sumWidths && diffCentersDoubled.X > 0;
			IntersectionInformation intersectInfo = new IntersectionInformation(
				otherAbove && (otherToLeft || otherToRight), // OtherAbove
                otherBelow && (otherToLeft || otherToRight), // OtherBelow
				otherToLeft && (otherAbove || otherBelow), // OtherToLeft
                otherToRight && (otherAbove || otherBelow)); // OtherToRight
			return intersectInfo;
		}

		/// <summary>
		/// Gives more information about FloatBox intersection, including whether the parameter intersected above, below, to the left, or to the right of the caller.
		/// </summary>
		public struct IntersectionInformation
		{
			/// <summary>
			/// Denotes that the parameter of the intersects call is above the caller.
			/// </summary>
			public bool OtherAbove { get; private set; }

			/// <summary>
			/// Denotes that the parameter of the intersects call is below the caller.
			/// </summary>
			public bool OtherBelow { get; private set; }

			/// <summary>
			/// Denotes that the parameter of the intersects call is to the left of the caller.
			/// </summary>
			public bool OtherToLeft { get; private set; }

			/// <summary>
			/// Denotes that the parameter of the intersects call is to the right of the caller.
			/// </summary>
			public bool OtherToRight { get; private set; }

			/// <summary>
			/// Constructs an IntersectionInformation object, where the parameters assign to the struct fields.
			/// </summary>
			/// <param name="otherAbove">The bool to assign to OtherAbove.</param>
			/// <param name="otherBelow">The bool to assign to OtherBelow.</param>
			/// <param name="otherToLeft">The bool to assign to OtherToLeft.</param>
			/// <param name="otherToRight">The bool to assign to OtherToRight.</param>
			public IntersectionInformation(bool otherAbove, bool otherBelow, bool otherToLeft, bool otherToRight) : this()
			{
				OtherAbove = otherAbove;
				OtherBelow = otherBelow;
				OtherToLeft = otherToLeft;
				OtherToRight = otherToRight;
			}

			/// <summary>
			/// Gives a string representation for this IntersectionInformation object.
			/// </summary>
			/// <returns>A string which gives details about this IntersectionInformation object.</returns>
			public override string ToString()
			{
				return string.Format("IntersectionInformation: OtherAbove {0}, OtherBelow {1}, OtherToLeft {2}, OtherToRight {3}", OtherAbove, OtherBelow, OtherToLeft, OtherToRight);
			}

			/// <summary>
			/// Implicitly converts an IntersectionInformation object to a bool, to allow intersects to serve as a more traditional Intersects method (analagous to Rectangle's Intersects method), when detailed intersection info is not needed.
			/// </summary>
			/// <param name="intersectInfo">The IntersectionInformation object to convert to a bool.</param>
			/// <returns>true if the IntersectionInformation object implies any intersection; false otherwise.</returns>
			public static implicit operator bool(IntersectionInformation intersectInfo)
			{
				return intersectInfo.OtherAbove || intersectInfo.OtherBelow || intersectInfo.OtherToLeft || intersectInfo.OtherToRight;
			}
		}
	}
}